#title: 部署时决定-插件机制
#index:0,1
#author:zozoh(zozohtnt@gmail.com)
--------------------------------------------------------------------------------------------------------
什么是插件
	<plugin_motivation.png>

	如果我们的项目依赖了一个接口，但是我们在开发时，真的没办法确定，部署的时候，到底采用哪个实现。

	比如 [log.man Nutz 的 Log]。 它在运行时，会判读当前运行环境 log4j 是否可用（有 log4j 的org.apache.log4j.Logger类)，
	如果没有，那么它就把日志信息输出到控制台上。它的实现，就是依靠的方式。

	但是，同复杂强大的 OSGI 插件体系不同，这里的插件只是强调，在部署时决定采用什么实现。在运行时，它是没
	办法更改的。因此 Nutz 虽然在编译时依赖了 Log4j，但是在运行时，没有 log4j 的 jar，依然能够工作的很好。

	也正因为，这个插件简单的令人发指。有兴趣的同学可以参看：
	[https://github.com/nutzam/nutz/tree/master/src/org/nutz/plugin org.nutz.plugin] 里面的源
	代码，我想几分钟你就会全部看完。
--------------------------------------------------------------------------------------------------------
简单使用插件
	比如有一个接口：
	{{{<Java>
	public interface Said{
		String say();
	}
	}}}
	你有两个实现类：
	 * 实现类 A
		{{{<Java>
		public class TomSaid implements Said{
			public String say(){
				return "I am Tom";
			}
		}
		}}}
	 * 实现类 B	
		{{{<Java>
		public class PeterSaid implements Said{
			public String say(){
				return "I am Peter";
			}
		}
		}}}
	这两个实现类分别放在两个 jar 包里，在你的工程部署时，负责部署的工程师很希望：
	 * 将 tom.jar 放到项目里，整个工程就会使用 TomSaid，
	 * 将 peter.jar 放到项目，整个工程就会使用 PeterSaid，
	 * 将两个 jar 都放到项目，PeterSaid 有更高的优先级
	
	{#A31;*怎样做到这一点呢？}

	首先我们需要在你的工程里为 TomSaid 实现一个插件：
	{{{<Java>
	public class TomSaidPlugin implements Plugin, Said{
		private Said said;
		public boolean canWork(){
			try {
				said = (Said)(Class.forName("com.you.app.TomSaid").newInstance());
				return true;
			} catch (Exception e) {}
			return false;
		}
		public String say(){
			return said.say();
		}
	}
	}}}

	同理，为 PeterSaid 也实现一个插件：
	{{{<Java>
	public class PeterSaidPlugin implements Plugin, Said{
		private Said said;
		public boolean canWork(){
			try {
				said = (Said)(Class.forName("com.you.app.PeterSaid").newInstance());
				return true;
			} catch (Exception e) {}
			return false;
		}
		public String say(){
			return said.say();
		}
	}
	}}}

	在调用代码里这样实现：
	{{{<Java>
	PluginManager<Said> plugins = new SimplePluginManager<Said>(
			"com.you.app.PeterSaidPlugin",
		    "com.you.app.TomSaidPlugin");
	Said said = plugins.get();
	System.out.println(said.say());
	}}}
	上面的代码既不依赖 PeterSaid，也不依赖 TomSaid，完全能满足部署工程师的要求。

	采用 SimplePluginManager 有几个注意事项：
	 * 插件实现类必须有一个默认的构造函数
	 * 插件实现类必须实现目标接口，在上例中就是 Said 接口
	 * 插件实现类实际上就是一个被适配目标的一个代理 （在这里，你可以套套“代理模式”）
	 * 构造函数参数的顺序，就是插件的优先级，第一个最优先
	
--------------------------------------------------------------------------------------------------------
与 Ioc 容器一起工作
	有些时候，你的 Plugin 实现类需要一些配置信息，某些配置信息可能相当复杂。 我们可以将插件同 [../ioc/hello.man Ioc 容器]
	联用（通过 `IocPlugManager<T>`）。
	
	比如我们修改一下上面的两个插件，让它们都需要被配置一个字段:
	{{{<Java>
	public class PeterSaidPlugin implements Plugin, Said {
		private String prefix;
		
		private Said said;

		public boolean canWork() {
			try {
				said = (Said) (Class.forName("com.you.app.PeterSaid").newInstance());
				return true;
			} catch (Exception e) {}
			return false;
		}

		public String say() {
			return prefix + said.say();
		}
	}
	}}}
	这两个插件，都需要一个 "prefix" 的属性	
	{{{<Java>
	public class TomSaidPlugin implements Plugin, Said {
		private String prefix;

		private Said said;

		public boolean canWork() {
			try {
				said = (Said) (Class.forName("com.you.app.TomSaid").newInstance());
				return true;
			} catch (Exception e) {}
			return false;
		}

		public String say() {
			return prefix + said.say();
		}
	}
	}}}
	
	在 [../ioc/hello.man Ioc 容器]的 Json 配置文件中:
	{{{<Json>
	// plugins.js
	{
		peter : {
			type	: 'com.you.app.PeterSaidPlugin',
			fields	: {
				prefix : 'Peter: '
			}
		},
		tom : {
			type	: 'com.you.app.TomSaidPlugin',
			fields	: {
				prefix : 'Tom: '
			}
		}
	}
	}}}
	调用代码改成：
	{{{
	Ioc ioc = new NutIoc(new JsonLoader("conf/plugins.js"));
	PluginManager<Said> plugins = new IocPluginManager<Said>(ioc, "peter", "tom");
	Said said = plugins.get();
	System.out.println(said.say());
	}}}
--------------------------------------------------------------------------------------------------------
最后一点说明
	我曾一度怀疑这个插件功能很无聊，因为它真的可以用“简陋”二字来形容（我们实现这个插件用的时间还没有我
	写这篇文档所用时间的一半），但是它毕竟给了你一条很简明的途径，
	让你的程序可以做到：
	
	{#F00;*部署时才决定某一个接口的实现}
	
	你用微小的代价（实现一个接口函数）获得下面两个好处：
	 * 你的应用很容易做到 {#00F;*模块化}
	 * 它几乎 {#00F;*没有侵入性}
	挺值得的，不是吗？ 这也是为什么我们将它放到 Nutz 的核心发布包里原因：
	（{/超值的东西才会被放到 Nutz 的核心包里}）
	
	如果你想做到运行时加载/卸载，这个“简陋”的小插件方案恐怕是帮不上你了。但是你真的需要吗？
	我注意到一个事实：Eclipse 采用的是 “OSGI”，但是在安装了一个插件之后它还是会建议你重启应用，每次我看到这个
	对话框，都觉得是对 “OSGI” 的一个讽刺。
